package me.lwn.auth.application;

import me.lwn.auth.common.crud.BaseManager;
import me.lwn.auth.domain.User;
import me.lwn.auth.infrastructure.repository.UserRepository;
import me.lwn.auth.resources.cmd.ChangePassword;
import me.lwn.auth.resources.cmd.ResetPassword;
import me.lwn.auth.security.oauth2.server.authorization.JdbcServerSettingRepository;
import me.lwn.auth.security.oauth2.server.authorization.ServerSetting;
import me.lwn.auth.security.provisioning.CustomizeJdbcUserDetailsManager;
import me.lwn.auth.security.utils.LocalMessageSource;
import me.lwn.auth.security.web.authentication.BadSmsCodeException;
import me.lwn.auth.security.web.authentication.RedisSmsVerifyService;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class UserManager extends BaseManager<User, String> {

    private final JdbcServerSettingRepository serverSettingRepository;
    private final CustomizeJdbcUserDetailsManager userDetailsManager;
    private final UserRepository repository;
    private final RedisSmsVerifyService smsVerifyService;
    private final PasswordEncoder passwordEncoder;
    private final MessageSourceAccessor message = LocalMessageSource.getAccessor();

    public UserManager(JdbcServerSettingRepository serverSettingRepository, UserDetailsService userDetailsService,
                       UserRepository repository, RedisSmsVerifyService smsVerifyService, PasswordEncoder passwordEncoder) {
        this.serverSettingRepository = serverSettingRepository;
        this.userDetailsManager = (CustomizeJdbcUserDetailsManager) userDetailsService;
        this.repository = repository;
        this.smsVerifyService = smsVerifyService;
        this.passwordEncoder = passwordEncoder;
    }

    private void check(String newPwd) {
        ServerSetting serverSetting = serverSettingRepository.get();
        if (serverSetting != null && !CollectionUtils.isEmpty(serverSetting.getPasswordRules())) {
            CheckRegexEnum regexEnum = CheckRegexEnum.get(serverSetting.getPasswordRules());
            Assert.notNull(regexEnum, "密码校验规则错误配置");
            Assert.isTrue(!newPwd.matches(String.format(regexEnum.getRegex(), serverSetting.getPasswordLength())),
                    String.format(regexEnum.getMsg(), serverSetting.getPasswordLength()));
        }
    }

    public void changePassword(ChangePassword changePassword) {

        check(changePassword.getNewPassword());
        userDetailsManager.changePassword(changePassword.getOldPassword(), changePassword.getNewPassword());
    }

    public void resetPassword(ResetPassword resetPassword) {
        check(resetPassword.getNewPassword());
        userDetailsManager.resetPassword(resetPassword);
    }

    public User add(User user) {
        user.setId(UUID.randomUUID().toString());
        return repository.save(user);
    }

    public void forget(String mobile, String code, String newPassword) {
        if (smsVerifyService.isInvalidForgetPasswordCode(mobile, code)) {
            throw new BadSmsCodeException(message.getMessage("UserManager.badSmsCode"));
        }
        User user = repository.getByMobile(mobile);
        user.setPassword(passwordEncoder.encode(newPassword));
        repository.save(user);
    }

    public User update(User user) {
        return repository.save(user);
    }

    public User getByUsername(String username) {
        return repository.getByUsername(username);
    }

    public User get(String id) {
        return repository.getById(id);
    }

    public long registerNumber() {
        return repository.count();
    }

    public void delete(String id) {
        repository.deleteById(id);
    }

    @Override
    protected UserRepository getRepository() {
        return repository;
    }

    enum CheckRegexEnum {

        NUMBER(1, "^(?=.*\\d)[\\d]{%d}$", "密码必须包含数字,长度不低于%d位"),
        CAPS_LOOK(2, "^(?=.*[a-z])(?=.*[A-Z])[A-Za-z]{%d}$", "密码必须包含大小写字母,长度不低于%d位"),
        NUMBER_CAPS_LOOK(3, "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[A-Za-z\\d]{%d}$", "密码必须包含数字、大小写字母,长度不低于%d位"),
        SPECIAL_CHAR(4, "^(?=.*[@$!%*?&])[@$!%*?&]{%d}$", "密码必须包含以下特殊字符(@$!%*?&),长度不低于%d位"),
        NUMBER_SPECIAL_CHAR(5, "^(?=.*\\d)(?=.*[@$!%*?&])[\\d@$!%*?&]{%d}$", "密码必须包含数字及以下特殊字符(@$!%*?&),长度不低于%d位"),
        CAPS_LOOK_SPECIAL_CHAR(6, "^(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*?&])[A-Za-z@$!%*?&]{%d}$", "密码必须包含大小写字母及以下特殊字符(@$!%*?&),长度不低于%d位"),
        NUMBER_CAPS_LOOK_SPECIAL_CHAR(7, "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{%d}$", "密码必须包含数字,长度不低于%d位");

        private final int key;
        private final String regex;
        private final String msg;

        CheckRegexEnum(int key, String regex, String msg) {
            this.key = key;
            this.regex = regex;
            this.msg = msg;
        }

        public static CheckRegexEnum get(Set<Integer> arr) {
            AtomicInteger i = new AtomicInteger(0);
            arr.forEach(rule -> i.set(i.get() | rule));
            return Arrays.stream(CheckRegexEnum.values()).filter(reg -> reg.getKey() == i.get()).findFirst().orElse(null);
        }

        public int getKey() {
            return key;
        }

        public String getRegex() {
            return regex;
        }

        public String getMsg() {
            return msg;
        }
    }
}